package comp6231.project.replica.dimitri;
/**
 * Distributed Retail System
 * Sep 30, 2012 
 */


import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * @author dimitri.tiago
 *
 * This class implements a retail store stock. This stock
 * makes use of synchronization using semaphores to
 * guarantee thread safety when interacting with stock items.
 */
public class RetailStoreStock
{
	private HashMap items;				// store (item, number of items)
	private HashMap itemSemaphores;		// store (item, itemSemaphore)
	private HashMap mutexSemaphores;	// store (item, mutexSemamphore)
	private HashMap readers;			// store (item, numOfReaders) 
	
	private final int INIT_ITEMS [];
	private final int INIT_NUM_ITEMS[];

	/**
	 * Default constructor initializes items and item semaphores used to
	 * guarantee exclusive concurrent access to stock items.
	 */
	public RetailStoreStock(String retailStoreLocation)
	{
		items 			= new HashMap();
		itemSemaphores 	= new HashMap();
		mutexSemaphores = new HashMap();
		readers			= new HashMap();
		
		// read stock from disk
		StockReader readStock = new StockReader();
		String filename       = String.format("%s-stock.txt", retailStoreLocation);
		readStock.openFile(filename);
		readStock.readRecords();
		INIT_ITEMS 	      	  = readStock.getItemIDs();
		INIT_NUM_ITEMS        = readStock.getNumberOfItems();  

		// initialize stock and individual item semaphores to guarantee exclusive access to each item.
		for (int i = 0; i < INIT_ITEMS.length; i++)
		{
			items.put(INIT_ITEMS[i], INIT_NUM_ITEMS[i]);

			BinarySemaphore itemSemaphore = new BinarySemaphore(true);
			itemSemaphores.put(INIT_ITEMS[i], itemSemaphore);
			
			BinarySemaphore mutexSemaphore = new BinarySemaphore(true);
			mutexSemaphores.put(INIT_ITEMS[i], mutexSemaphore);
			
			readers.put(INIT_ITEMS[i], 0);
		}
	}

	public int remItemsFromStock(int itemID, int numberOfItems)
	{
		int itemsRemoved = 0;

		// obtain item semaphore and remove items from stock
		BinarySemaphore itemSemaphore = (BinarySemaphore) itemSemaphores.get(itemID);
		if (itemSemaphore == null)
		{
			// there is no semaphore for item (item does not exist in this store).
		}
		else
		{
			// attempt to remove items from stock 
			itemSemaphore.Wait(); // entry to critical section. attempt to acquire semaphore

			int itemsInStock = (Integer) items.get(itemID);	// get number of items in stock
			if (itemsInStock == 0)
			{
				// there are no more items for specified item in stock
			}
			else
			{
				// attempt to remove number of items from items in stock
				if ( (itemsInStock - numberOfItems) >= 0 )
				{
					// there are enough items to satisfy request. remove items from stock
					itemsRemoved = numberOfItems;
					itemsInStock -= numberOfItems;
				}
				else
				{
					// there are not enough items to satisfy request. 
					// remove maximum number of items from stock 
					itemsRemoved = itemsInStock;
					itemsInStock = 0;
				}

				items.put(itemID, itemsInStock); // update stocked number of items
			}

			itemSemaphore.Signal(); // exit of critical section. release semaphore			
		}

		return itemsRemoved;
	}

	public int addItemsToStock(int itemID, int numberOfItems)
	{
		int itemsAdded = 0;

		// obtain item semaphore and add items to stock
		BinarySemaphore itemSemaphore = (BinarySemaphore) itemSemaphores.get(itemID);
		if (itemSemaphore == null)
		{
			// there is no semaphore for item (item does not exist in this store).
		}
		else
		{
			// add items to stock 
			itemSemaphore.Wait(); // entry to critical section. attempt to acquire semaphore

			int itemsInStock = (Integer) items.get(itemID);	// get number of items in stock
			itemsInStock     += numberOfItems;		// return items to stock
			items.put(itemID, itemsInStock); 		// update stocked number of items
			itemsAdded 	     = numberOfItems;

			itemSemaphore.Signal(); // exit of critical section. release semaphore
		}

		return itemsAdded;
	}

	public int checkItemsStock(int itemID)
	{
		int itemsInStock = 0;

		int numOfReaders = 0; // number of readers in critical section for item
		
		// obtain item semaphore and remove items from stock
		BinarySemaphore itemSemaphore  = (BinarySemaphore) itemSemaphores.get(itemID);
		BinarySemaphore mutexSemaphore = (BinarySemaphore) mutexSemaphores.get(itemID);
		if ( (itemSemaphore == null) || (mutexSemaphore == null) )
		{
			// there is no semaphore for item (item does not exist in this store).
		}
		else
		{
			// attempt to remove items from stock 
			mutexSemaphore.Wait();
			numOfReaders = (Integer) readers.get(itemID); // increment number of readers for item
			numOfReaders = numOfReaders + 1;
			readers.put(itemID, numOfReaders);
			if (numOfReaders == 1)
			{
				itemSemaphore.Wait();
			}
			mutexSemaphore.Signal();
			
			// entering to critical section
			itemsInStock = (Integer) items.get(itemID); // get number of items in stock
			// leaving critical section
			
			mutexSemaphore.Wait();
			numOfReaders = (Integer) readers.get(itemID); // decrement number of readers for item
			numOfReaders = numOfReaders - 1;
			readers.put(itemID, numOfReaders);
			if (numOfReaders == 0)
			{
				itemSemaphore.Signal();
			}
			mutexSemaphore.Signal();			
		}

		return itemsInStock;
	}
	
	public HashMap exportItemsStock()
	{
		return items;
	}
	
	public void importItemsStock(Map<String,Integer> newItems)
	{
		// re-set items stock and synchronization mechanisms
		items 		    = new HashMap();
		itemSemaphores 	= new HashMap();
		mutexSemaphores = new HashMap();
		readers			= new HashMap();

		// copy new stock into items stock
		Map<String, Integer> map = newItems;
		Iterator<Map.Entry<String, Integer>> entries = map.entrySet().iterator();
		while (entries.hasNext()) 
		{
			// copy next item
		    Map.Entry<String, Integer> entry = entries.next();
		    items.put(Integer.parseInt(entry.getKey()), entry.getValue()); 
		    
		    // add synchronization for item
		    itemSemaphores.put(Integer.parseInt(entry.getKey()), new BinarySemaphore(true)); 
		    mutexSemaphores.put(Integer.parseInt(entry.getKey()), new BinarySemaphore(true));
		    readers.put(Integer.parseInt(entry.getKey()), 0);
		}
		
		//TODO: test code
		System.out.println(items.toString());
	}
}
